/*
   Part of the libABF project.

   libABF is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the
   Free Software Foundation, Inc.,
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/
#include "abf_format_lex.h"
#include "abf_util.h"

#include <stdlib.h>
#include <string.h>

#define CHECK_STATE(state) assert( state )

abf_lex_state_t* abf_lex_create(abf_lex_config_t* config)
{
    abf_lex_state_t* state = (abf_lex_state_t*) CHECKED_ALLOC(sizeof(abf_lex_state_t));
    state->Config = config;
    state->Buffer = 0;
    state->Size = 0;
    state->Token = ABF_LEX_EOF;
    return state;
}

void abf_lex_destroy(abf_lex_state_t* state)
{
    CHECK_STATE(state);
    if (state->Buffer)
        free(state->Buffer);
    free(state);
}

abf_status abf_lex_read (abf_lex_state_t* state, const char* filename)
{
    return abf_read_into(filename, &state->Buffer, &state->Capacity, &state->Size);
}

#define LEX_OK(state) (!(state->Token == ABF_LEX_EOF || state->Token == ABF_LEX_ERROR))

int is_in(int ch, const char* l)
{
    for(; 0 != *l;++l)
        if( ch == *l )
            return 1;
    return 0;
}

int abf_lex_next_char(abf_lex_state_t* state, int* ch)
{
    if (state->Ptr>= state->Buffer + state->Size)
    {
        state->Token = ABF_LEX_EOF;
        return 0;
    }
    *ch = *state->Ptr;
    state->Ptr++;
    return 1;
}

int abf_lex_prev_char(abf_lex_state_t* state, int* ch)
{
    if (state->Ptr==state->Buffer)
    {
        state->Token = ABF_LEX_ERROR;
        return 0;
    }
    --state->Ptr;
    *ch = *state->Ptr;
    return 1;
}

int abf_lex_get_newline (abf_lex_state_t* state)
{
    return 1;
}

int abf_lex_peek(abf_lex_state_t* state, const char* s)
{
    char* ptr = state->Ptr;
    uint32_t n;
    if (!s)
        return 0;
    n = strlen(s);
    if (ptr + n >= state->Buffer + state->Size)
        return 0;
    if (strncmp(s,ptr,n) == 0)
    {
        state->Ptr += n;
        return 1;
    }
    return 0;
}

void abf_lex_skip_comment (abf_lex_state_t* state)
{
    int ch;
    while (LEX_OK(state))
    {
        if (abf_lex_peek(state, state->Config->EndComment))
        {
            state->Ptr += strlen(state->Config->EndComment);
            return;
        }
        if (!abf_lex_next_char(state,&ch))
            return;
        if (ch=='\n')
            abf_lex_get_newline(state);
    }
}


abf_lex_token abf_lex_get_space (abf_lex_state_t* state)
{
    int ch;
    while (LEX_OK(state))
    {
        if (!abf_lex_next_char(state, &ch))
            return state->Token;

        while (ch == ' ' || ch == '\n' || ch == '\r')
        {
            if (ch == '\n')
                abf_lex_get_newline(state);

            if (!abf_lex_next_char(state, &ch))
                return state->Token;
        }

        if( abf_lex_peek(state, state->Config->StartComment) )
        {
            abf_lex_skip_comment(state);
        }
    }

    if (LEX_OK(state))
    {
        if (!abf_lex_prev_char(state,&ch))
            return ABF_LEX_ERROR;
    }
    return ABF_LEX_UNKNOWN;
}

abf_lex_token abf_lex_get_string (abf_lex_state_t* state)
{
    char* ptr = state->Ptr;
    int ch;
    int size = 0;
    while(!abf_lex_peek (state, state->Config->StartString))
        abf_lex_next_char(state, &ch);
    if (!LEX_OK(state))
        return state->Token;
    size = state->Ptr - ptr;
    state->AsString = (char*) CHECKED_ALLOC(size + 1);
    memcpy(state->AsString,ptr,size);
    state->AsString[size] = 0;
    state->Token = ABF_LEX_STRING;
    return state->Token;
}

abf_lex_token abf_lex_get_number (abf_lex_state_t* state)
{
    char buffer[64];
    int ch;
    int dot_count = 0;
    int i = 0;
    abf_lex_next_char(state, &ch);
    do
    {
        if (ch == '.')
            dot_count += 1;

        buffer[i] = ch;
        ++i;
        abf_lex_next_char(state, &ch);
    } while( is_in(ch, ".+-0123456789E") && LEX_OK(state) && i < 64);
    buffer[i] = 0;

    if (LEX_OK(state))
        abf_lex_prev_char(state, &ch);

    if (dot_count)
    {
        state->Token = ABF_LEX_FLOAT;
        state->AsFloat = strtod(buffer,0);
        state->AsInt = state->AsFloat;
    } else
    {
        state->Token = ABF_LEX_INT;
        state->AsInt = strtol(buffer,0,0);
    }
    return state->Token;
}


abf_lex_token abf_lex_next_token (abf_lex_state_t* state)
{
    int ch;
    CHECK_STATE(state);
    state->Token = abf_lex_get_space (state);
    if (!LEX_OK(state))
        return state->Token;

    if (abf_lex_peek (state,state->Config->StartString))
    {
        return abf_lex_get_string (state);
    }

    if (abf_lex_peek (state,state->Config->StartStruct))
    {
        state->Token = ABF_LEX_START_STRUCT;
        return state->Token;
    }

    if (abf_lex_peek (state,state->Config->EndStruct))
    {
        state->Token = ABF_LEX_END_STRUCT;
        return state->Token;
    }

    if (abf_lex_peek (state,state->Config->StartList))
    {
        state->Token = ABF_LEX_START_LIST;
        return state->Token;
    }

    if (abf_lex_peek (state,state->Config->EndList))
    {
        state->Token = ABF_LEX_END_LIST;
        return state->Token;
    }

    abf_lex_next_char (state, &ch);
    if (is_in (ch, ".+-0123456789"))
    {
        abf_lex_prev_char (state, &ch);
        return abf_lex_get_number (state);
    }

    if (is_in (ch, ","))
    {
        state->Token = ABF_LEX_LIST_SEPARATOR;
        return state->Token;
    }

    if (is_in (ch, "="))
    {
        state->Token = ABF_LEX_ASSIGN;
        return state->Token;
    }


    return state->Token;
}
